#include "Functions/PrintTable.h"
#include "Functions/File.h"
#include "Functions/TerminalIO.h"
#include <cmath>
#include <iomanip>
#include <iostream>
#include <string>
using namespace std;
using namespace BOSS_Afterburner::File;
using namespace BOSS_Afterburner::TerminalIO;

EndTableRow TableRow::endrow;

void SetStreamAlign(ostream& ofs, TableColumn::EAlign align);

void Table::SetWidths(const TableRow& row)
{
  for(size_t i = 0; i < row.fCells.size(); ++i)
    SetWidth(i, row[i]);
}

void Table::SetWidth(size_t i, const std::string& str)
{
  if(i >= fColumns.size()) fColumns.resize(i + 1);
  str.size() > fColumns[i].Width ? fColumns[i].Width = str.size() : true;
}

void TableRow::SetCells(const vector<string>& v)
{
  fCells.clear();
  for(const auto& col : v)
    AddCell(col);
}

void Table::Sort()
{
  fRows.sort([](TableRow const& a, TableRow const& b) {
    return a.SortValue != b.SortValue ? a.SortValue > b.SortValue : a.Name < b.Name;
  });
}

void Table::Clean()
{
  while(ContainsEmptyLines())
    for(auto it = fRows.begin(); it != fRows.end(); ++it)
      if(it->IsEmpty())
      {
        fRows.erase(it);
        break;
      }
}

Bool_t Table::ContainsEmptyLines() const
{
  for(const auto& row : fRows)
    if(row.IsEmpty()) return true;
  return false;
}

void Table::SetAlign(size_t i, TableColumn::EAlign align)
{
  if(i < fColumns.size()) fColumns[i].Align = align;
}

void Table::SetAlign(std::vector<TableColumn::EAlign> v)
{
  for(size_t i = 0; i < v.size(); ++i)
    SetAlign(i, v[i]);
}

void Table::SetLastSortValue(Long64_t val)
{
  if(!fRows.size()) return;
  fRows.back().SortValue = val;
}

void Table::SetLastName(const string& name)
{
  if(!fRows.size()) return;
  fRows.back().Name = name;
}

void Table::SetHeader(std::vector<std::string> v)
{
  fHeader.SortValue = 1;
  fHeader.SetCells(v);
  SetWidths(fHeader);
}

void Table::Print() const
{
  if(!fRows.size())
  {
    PrintWarning("Empty table");
    return;
  }
  cout << endl;
  if(fHeader.SortValue)
  {
    fHeader.Print(fColumns);
    PrintLine();
  }
  for(const auto& row : fRows)
  {
    if(row.IsEmpty()) continue;
    if(row[0].compare("N_events") == 0) PrintLine();
    row.Print(fColumns);
  }
  if(fHeader.SortValue) PrintLine();
  cout << endl;
}

void TableRow::Print(const vector<TableColumn>& format) const
{
  cout << " | ";
  for(size_t i = 0; i < fCells.size(); ++i)
  {
    if(i < format.size()) switch(format[i].Align)
      {
        case TableColumn::Left: cout << left; break;
        case TableColumn::Right: cout << right; break;
        case TableColumn::Internal: cout << internal; break;
      }
    cout << setw(format[i].Width) << fCells[i] << " | ";
    cout << left;
  }
  cout << std::endl;
}

void TableRow::Print() const
{
  cout << " | ";
  for(size_t i = 0; i < fCells.size(); ++i)
    cout << fCells[i] << " | ";
  cout << endl;
}

void Table::PrintLine() const
{
  cout << setfill('-');
  cout << " | ";
  for(size_t i = 0; i < fColumns.size(); ++i)
    cout << right << setw(fColumns[i].Width + 3) << " | ";
  cout << setfill(' ');
  cout << left << endl;
}

void Table::WriteLaTeX(string filename) const
{
  if(!fRows.size())
  {
    PrintWarning("Empty table");
    return;
  }
  filename = SetOutputFilename(filename.c_str(), "tex");
  ofstream ofs(filename.c_str(), std::ofstream::out);
  ofs << "\\begin{tabular}";
  if(fColumns.size())
  {
    ofs << "{";
    for(const auto& it : fColumns)
      switch(it.Align)
      {
        case TableColumn::Left: ofs << "l"; break;
        case TableColumn::Right: ofs << "r"; break;
        case TableColumn::Internal: ofs << "c"; break;
      }
    ofs << "}";
  }
  ofs << endl;
  if(fHeader.SortValue)
  {
    fHeader.WriteLaTeXBold(ofs);
    ofs << "  \\hline" << endl;
  }
  for(const auto& row : fRows)
    if(!row.IsEmpty()) row.WriteLaTeX(ofs, fColumns);
  ofs << "\\end{tabular}";
  ofs.close();
}

void Table::Write(string filename, const char* delim) const
{
  if(!fRows.size())
  {
    PrintWarning("Empty table");
    return;
  }
  filename = SetOutputFilename(filename.c_str(), "txt");
  ofstream ofs(filename.c_str(), std::ofstream::out);
  if(fHeader.SortValue) fHeader.Write(ofs, delim);
  for(const auto& row : fRows)
    if(!row.IsEmpty()) row.Write(ofs, delim);
  ofs.close();
}

void TableRow::WriteLaTeX(ofstream& ofs, const std::vector<TableColumn>& format) const
{
  ofs << "  ";
  for(size_t i = 0; i < fCells.size(); ++i)
  {
    if(i < format.size()) SetStreamAlign(ofs, format[i].Align);
    ofs << setw(format[i].Width) << fCells[i];
    if(i < fCells.size() - 1)
      ofs << " & ";
    else
      ofs << " \\\\";
    ofs << left;
  }
  ofs << endl;
}

void TableRow::WriteLaTeX(ofstream& ofs) const
{
  ofs << "  ";
  for(size_t i = 0; i < fCells.size(); ++i)
  {
    ofs << fCells[i];
    if(i < fCells.size() - 1)
      ofs << " & ";
    else
      ofs << " \\\\";
  }
  ofs << endl;
}

void TableRow::WriteLaTeXBold(ofstream& ofs) const
{
  ofs << "  ";
  for(size_t i = 0; i < fCells.size(); ++i)
  {
    ofs << "\\textbf{";
    ofs << fCells[i];
    ofs << "}";
    if(i < fCells.size() - 1)
      ofs << " & ";
    else
      ofs << " \\\\";
  }
  ofs << endl;
}

void TableRow::Write(ofstream& ofs, const char* delim) const
{
  for(size_t i = 0; i < fCells.size(); ++i)
  {
    ofs << fCells[i];
    if(i < fCells.size() - 1) ofs << delim;
  }
  ofs << endl;
}

const TableRow& Table::operator[](size_t i) const
{
  size_t j = 0;
  for(const TableRow& row : fRows)
  {
    if(j == i) return row;
    ++j;
  }
  PrintWarning(Form("Could not find TableRow %u/%u", i, fRows.size()));
  return TableRow();
}

const TableRow& Table::operator[](const string& name) const
{
  for(const TableRow& row : fRows)
    if(row.Name.compare(name)) return row;
  PrintWarning(Form("Could not find TableRow \"%s\"", name.c_str()));
  return TableRow();
}

const string& TableRow::operator[](size_t i) const
{
  if(i < fCells.size())
    return fCells[i];
  else
    return "";
}

void Table::AddRow(const TableRow& row)
{
  SetWidths(row);
  fRows.push_back(row);
}

void Table::AddRow()
{
  TableRow row;
  AddRow(row);
}

void SetStreamAlign(ostream& ofs, TableColumn::EAlign align)
{
  switch(align)
  {
    case TableColumn::Left: ofs << left; break;
    case TableColumn::Right: ofs << right; break;
    case TableColumn::Internal: ofs << internal; break;
  }
}